#!/usr/bin/env ruby

# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include_relative 'common.irt'

$available_regs = $temps_mask + :arg0 + :arg1
# there are 2 temp registers are using in case of arm64 (instead of 3 for x86_64 and arm32)
# that's why one callee-saved register is added to the mask
if Options.arch == :arm64
  $available_regs = $available_regs + :callee1
end

AllocateObjectTlabValidation = {
  spills_count_max: { default: 2, arm32: 9999 },  # TODO(msherstennikov): set to 0 once regalloc supports smart temps
  code_size_max: { arm64: 116, x86_64: 477, arm32: 9999 }
  # TODO(msherstennikov): revert back code size values, once regalloc supports smart temps
  # code_size_max: { arm64: 100, x86_64: 125, arm32: 9999 }
}

AllocateArrayTlabValidation = {
  spills_count_max: { default: 2, arm32: 9999 },  # TODO(msherstennikov): set to 0 once regalloc supports smart temps
  code_size_max: { arm64: 136, x86_64: 476, arm32: 9999 }
  # TODO(msherstennikov): revert back code size values, once regalloc supports smart temps
  # code_size_max: { arm64: 128, x86_64: 145, arm32: 9999 }
}

function(:AllocateObjectTlab,
         params: {klass: 'ref', size: 'word'},
         regmap: $full_regmap,
         mode: [:FastPath],
         regalloc_set: $available_regs,
         validate: AllocateObjectTlabValidation) {

  if Options.arch == :arm32
    Intrinsic(:UNREACHABLE).Terminator.void
    ReturnVoid().void
    next
  end

  # Load pointer to the TLAB from TLS
  tlab_ptr := LoadI(%tr).Imm(Constants::TLAB_OFFSET).ptr

  # Load pointer to the start address of free memory in the TLAB
  start := LoadI(tlab_ptr).Imm(Constants::TLAB_CUR_FREE_POSITION_OFFSET).ptr

  # Load pointer to the end address of free memory in the TLAB
  tls_end := LoadI(tlab_ptr).Imm(Constants::TLAB_MEMORY_END_ADDR_OFFSET).ptr

  tls_size := Sub(tls_end, start).word

  If(tls_size, size).LT.Unlikely.b {
    Intrinsic(:SLOW_PATH_ENTRY, klass, size).AddImm(Constants::CREATE_OBJECT_BY_CLASS_SLOW_PATH).AddImm(Constants::CREATE_OBJECT_BY_CLASS_USUAL_BRIDGE).Terminator.ptr
    Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
  } Else {
    call_runtime_save_all(Constants::WRITE_TLAB_STATS_NO_BRIDGE, start, size).void if defines.DEBUG
    if defines.__SANITIZE_ADDRESS__ || defines.__SANITIZE_THREAD__
      call_runtime_save_all(Constants::ANNOTATE_SANITIZERS_NO_BRIDGE, start, size).void
    end
    new_start := Add(start, size).ptr
    StoreI(start, klass).Imm(Constants::OBJECT_CLASS_OFFSET).ref
    addr := Add(tlab_ptr, Constants::TLAB_CUR_FREE_POSITION_OFFSET).ptr
    StoreI(addr, new_start).Imm(0).Volatile.ptr
    Return(start).ptr
  }
}

# Allow three temps, two arguments registers and one callee saved register
$available_regs = $available_regs + :callee0

def AllocateArrayTlab(element_size)
  function("AllocateArrayTlab#{element_size}".to_sym,
           params: {klass: 'ref', elements_num: 'word'},
           regmap: $full_regmap,
           mode: [:FastPath],
           regalloc_set: $available_regs,
           validate: AllocateArrayTlabValidation) {
    if Options.arch == :arm32
      ReturnVoid().void
      next
    end
    elements_num := And(elements_num, "0x00000000ffffffff").word
    if element_size == 8
      size := elements_num
    elsif element_size == 16
      size := Shl(elements_num, 1).word
    elsif element_size == 32
      size := Shl(elements_num, 2).word
    elsif element_size == 64
      size := Shl(elements_num, 3).word
    else
      raise "Wrong element size #{element_size}"
    end
    # Add sizeof(Array) and do align
    size := Add(size, "DEFAULT_ALIGNMENT_IN_BYTES - 1 + CORETYPES_ARRAY_CLASS_SIZE").word
    size := And(size, "(~(DEFAULT_ALIGNMENT_IN_BYTES - 1))").word

    # Load pointer to the TLAB from TLS
    tlab_ptr := LoadI(%tr).Imm(Constants::TLAB_OFFSET).ptr

    # Load pointer to the start address of free memory in the TLAB
    start := LoadI(tlab_ptr).Imm(Constants::TLAB_CUR_FREE_POSITION_OFFSET).ptr

    # Load pointer to the end address of free memory in the TLAB
    tls_end := LoadI(tlab_ptr).Imm(Constants::TLAB_MEMORY_END_ADDR_OFFSET).ptr

    tls_size := Sub(tls_end, start).word

    If(tls_size, size).LT.Unlikely.b {
      Intrinsic(:SLOW_PATH_ENTRY, klass, elements_num).AddImm(Constants::CREATE_ARRAY_SLOW_PATH).AddImm(Constants::CREATE_ARRAY_USUAL_BRIDGE).Terminator.ptr
      Intrinsic(:UNREACHABLE).Terminator.void if defines.DEBUG
    } Else {
      call_runtime_save_all(Constants::WRITE_TLAB_STATS_NO_BRIDGE, start, size).void if defines.DEBUG
      if defines.__SANITIZE_ADDRESS__ || defines.__SANITIZE_THREAD__
        call_runtime_save_all(Constants::ANNOTATE_SANITIZERS_NO_BRIDGE, start, size).void
      end
      new_start := Add(start, size).ptr
      StoreI(start, klass).Imm(Constants::OBJECT_CLASS_OFFSET).ref
      StoreI(start, elements_num).Imm(Constants::ARRAY_LENGTH_OFFSET).word
      addr := Add(tlab_ptr, Constants::TLAB_CUR_FREE_POSITION_OFFSET).ptr
      StoreI(addr, new_start).Imm(0).Volatile.ptr
      Return(start).ptr
    }
  }
end

AllocateArrayTlab(8)
AllocateArrayTlab(16)
AllocateArrayTlab(32)
AllocateArrayTlab(64)
